<?php

require_once "restful/EntitySerializer.php";
require_once "restful/RestfulServiceConsumer.php";

class EmptyResponseException extends Exception {

    public function __construct($url) {
        parent::__construct("[ $url ] Un expected empty response.");
    }

}

/**
 * A basic abstract implementation of a web service client, intended to ease
 * the construction of a web service consumer.
 *
 * @author Virtualidad
 * @version 1.0
 * @created 29-dic-2010 09:57:48 a.m.
 */
abstract class BaseService {

    /**
     *
     * @var RestfulServiceConsumer
     */
    protected $client;
    private $mimeType = "application/json";
    private $requestMime = "application/json";
    private $responseMime = "application/json";
    /**
     * This is a bit that indicates if data received from service should be
     * decoded to the corresponding entities.
     * 
     * @var boolean
     */
    public $shouldDecode = true;

    function __construct($serviceBaseURL) {
        $this->client = new RestfulServiceConsumer($serviceBaseURL);
    }

    /**
     * Instantiates a serializer object with the given configuration values.
     * 
     * @param string $mimetype The mime/type of the serializer.
     * @param string $targetClass [Optional] The class for wich the object should serialize/deserialize to/from.
     * @param array $mapping [Optional] The mapping of the target class.
     * @param array $extraConfig [Optional] <p>Array with the configuration to override the other characteristics of the serializer.</p>
     * <p>The should have this form: </p>
     * <code>
     *  array(
     *      "getPrefix"=>"myGetPrefix", //Normally "get", but others may preffer "Get" or "get_" or symply an empty string.
     *      "setPrefix"=>"mySetPrefix", //Normally "get", but others may preffer "Set" or "set_" or symply an empty string.
     *      "propertiesArePublic"=>true //Normally false.
     * );
     * </code>
     * @return EntitySerializer
     */
    protected function getSerializer($mimetype, $targetClass = "stdClass",
            array $mapping = array(), array $extraConfig = array()) {
        $serializer = MimeSerializerFactory::getByMimeType($this->mimeType, $targetClass, $mapping);
        $this->configureSerializer($serializer, $extraConfig);
        return $serializer;
    }

    private function configureSerializer(EntitySerializer &$serializer,
            array $config = array()) {
        if (isset($config["getSuffix"])) {
            $serializer->setGetPrefix($config["getPrefix"]);
        }
        if (isset($config["setSuffix"])) {
            $serializer->setSetPrefix($config["setPrefix"]);
        }
        if (isset($config["propertiesArePublic"])) {
            $serializer->setPropertiesArePublic($config["propertiesArePublic"]);
        }
    }

    public abstract function getServiceClass();

    public function invokeService($functionName, $data = "",
            $exceptionOnEmpty = false) {

        $config = array(
            "serviceClass" => $this->getServiceClass(),
            "acceptType" => $this->getRequestMime(),
            "contentType" => $this->getResponseMime(),
            "method" => "POST",
            "data" => $data,
        );

        try {
            $response = $this->client->invokeService($functionName, $config);
        } catch (RequestFailedException $ex) {
            //Try it again just once.
            if ($ex->getErrorCode() == RequestCodes::OPERATION_TIMEDOUT) {
                $response = $this->client->invokeService($functionName, $config);
            } else {
                throw $ex;
            }
        }


        if ($exceptionOnEmpty && ($response == "" || $response == null)) {
            throw new EmptyResponseException($this->client->getLastURI());
        }

        return $response;
    }

    /**
     * Desides if it's necesary to deserialize a given response and do it so. This
     * method is limited to a single object response.
     *
     * @param string $response A response string wich should be in the format given by the mime type.
     * @param string $mime The mime/type of the response.
     * @param string $targetClass [Optional] The target class to deserialize.
     * @param array $mapping [Optional] The mapping of the target object.
     * @param array $extraConfig The configuration for the rest of the characteristics.
     * @return mixed | string An instance of the target object if shouldDecode bit is set to true.
     */
    public function processSingleObjectResponse($response, $mime,
            $targetClass = "stdClass", array $mapping = array(),
            array $extraConfig = array()) {
        if ($this->shouldDecode) {
            $serializer = $this->getSerializer($mime, $targetClass, $mapping, $extraConfig);
            return $serializer->deSerialize($response);
        } else {
            return $response;
        }
    }

    public function processArrayResponse($response, $mime,
            $targetClass = "stdClass", $mapping = array(),
            array $extraConfig = array()) {
        if ($this->shouldDecode) {
            $serializer = $this->getSerializer($mime, $targetClass, $mapping, $extraConfig);
            return $serializer->deSerializeArray($response);
        } else {
            return $response;
        }
    }

    public function getMimeType() {
        return $this->mimeType;
    }

    public function setMimeType($mimeType) {
        $this->mimeType = $mimeType;
    }

    public function getRequestMime() {
        return $this->requestMime;
    }

    public function setRequestMime($requestMime) {
        $this->requestMime = $requestMime;
    }

    public function getResponseMime() {
        return $this->responseMime;
    }

    public function setResponseMime($responseMime) {
        $this->responseMime = $responseMime;
    }

    public function getLastRequestedURL() {
        return $this->client->getLastURI();
    }

    public function setRawMode($raw) {
        $this->shouldDecode = !$raw;
    }

    public function isRawMode() {
        return!$this->shouldDecode;
    }

    public function addHeaderVariable($name, $value) {
        $this->client->getRequester()->addHeaderVariable($name, $value);
    }

}

?>